//
//  ADCacheTool.swift
//  ADKit
//
//  Created by Adoma on 2023/1/13.
//

import Alamofire
import RxSwift

/// by ADKit
public struct ADCacheTool {
    
    public static var freeDiskSpace: UInt64 {
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let attr = try! FileManager.default.attributesOfFileSystem(forPath: path)
        return attr[.systemFreeSize] as? UInt64 ?? 0
    }
    
    public static var totalDiskSpace: UInt64 {
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let attr = try! FileManager.default.attributesOfFileSystem(forPath: path)
        return attr[.systemSize] as? UInt64 ?? 0
    }
    
    public static var cacheSize: UInt64 {
        
        let manager = FileManager.default
        
        guard let cachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first,
              let subpaths = manager.subpaths(atPath: cachePath) else {
            return 0
        }
        
        var size: UInt64 = 0
        let files = subpaths.map({ cachePath + "/\($0)" })
        
        for file in files {
            
            let attr = try? manager.attributesOfItem(atPath: file)
            let type = attr?[.type] as? FileAttributeType
            
            if type == .typeDirectory {
                continue
            }
            
            size += (attr?[.size] as? UInt64) ?? 0
        }
        
        return size
    }
    
    public static func clearCache(completed: ((UInt64)->Void)? = nil) {
        
        DispatchQueue.global(qos: .default).async {
            
            let manager = FileManager.default
            var size = cacheSize
            
            guard let cachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first,
                  let subpaths = manager.subpaths(atPath: cachePath) else {
                return
            }
            
            let files = subpaths.map({ cachePath + "/\($0)" })
            
            for file in files {
                
                let type = try? manager.attributesOfItem(atPath: file)[.type] as? FileAttributeType
                if type == .typeDirectory {
                    continue
                }
                
                do {
                    try manager.removeItem(atPath: file)
                } catch {
                    ADLogger(adkit: self)
                        .debug("ADCacheTool clear file: \(file) failed!")
                }
            }
            
            size -= cacheSize
            
            completed?(size)
        }
    }
    
    
    public static func filePath(for source: URL, queue: DispatchQueue = .main) -> Single<URL> {
        
        return Single.create { observer -> Disposable in
            
            var name = source.absoluteString.md5()
            if source.pathExtension.isEmpty == false {
                name += ".\(source.pathExtension)"
            }
            
            let cacheDirectory = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last! as NSString
            
            let path = cacheDirectory.appendingPathComponent("ADCacheTool/\(name)")
            
            let destination = NSURL(fileURLWithPath: path) as URL
            
            if FileManager.default.fileExists(atPath: path) {
                
                queue.async {
                    observer(.success(destination))
                }
                
                return Disposables.create {}
                
            } else {
                
                let request = FileDownloader.shared.download(from: source,
                                                             destinationURL: destination,
                                                             queue: queue) { result in
                    switch result {
                    case .success(let url):
                        observer(.success(url))
                    case .failure(let error):
                        observer(.failure(error))
                    }
                }
                
                return Disposables.create {
                    request?.cancel()
                }
            }
        }
    }
}
